/**
 * MINAPL Compiler.
 * Copyright 2012 Michael A. Muller
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#include "Tokenizer.h"

#include <ctype.h>
#include <stdlib.h>

#include "Buffer.h"
#include "Token.h"

using namespace minapl;

namespace {
    Buffer buffer = {0, 0, 0};
}

void Tokenizer::scanToken(Token *tok) {
    int state = 0;
    int available = 0;
    int intVal = 0;
    tok->lineNum = lineNum;
    while (1) {
        int ch = getc(fp);
        if (ch == -1) {
            tok->type = TOK_END;
            return;
        }

        // increment the line number on newlines, unless we're in a state
        // where it's going to get ungetc'ed
        if (ch == '\n' && state != 2 && state != 3) {
            ++lineNum;
            tok->lineNum = lineNum;
        }

        switch (state) {
            case 0:
                if (isspace(ch))
                    ;
                else if (ch == '#')
                    state = 4;
                else if (ch == '\'') {
                    buffer.startToken();
                    state = 1;
                } else if (ch == ';') {
                    tok->type = TOK_SEMI;
                    return;
                } else if (ch == '(') {
                    tok->type = TOK_LPAREN;
                    return;
                } else if (ch == ')') {
                    tok->type = TOK_RPAREN;
                    return;
                } else if (ch == '{') {
                    tok->type = TOK_LCURLY;
                    return;
                } else if (ch == '}') {
                    tok->type = TOK_RCURLY;
                    return;
                } else if (ch == '.') {
                    tok->type = TOK_DOT;
                    return;
                } else if (isdigit(ch)) {
                    tok->type = TOK_INT;
                    intVal = ch - '0';
                    state = 3;
                } else {
                    buffer.startToken();
                    buffer.addChar(ch);
                    state = 2;
                }
                break;

            // string
            case 1:
                if (ch == '\'') {
                    buffer.addChar(0);
                    tok->type = TOK_STR;
                    tok->text = buffer.start;
                    tok->size = buffer.cur - buffer.start;
                    buffer.consume();
                    return;
                } else {
                    buffer.addChar(ch);
                }
                break;

            // identifier
            case 2:
                if (isspace(ch) || ch == '\'' || ch == '{' || ch == '}' ||
                    ch == '(' || ch == ')' || ch == ';' || ch == '.'
                    ) {
                    ungetc(ch, fp);
                    tok->type = TOK_ID;
                    tok->text = buffer.start;
                    tok->size = buffer.cur - buffer.start;
                    buffer.addChar(0);
                    buffer.consume();
                    return;
                } else {
                    buffer.addChar(ch);
                }
                break;

            // integer
            case 3:
                if (isdigit(ch)) {
                    intVal = intVal * 10 + ch - '0';
                } else {
                    ungetc(ch, fp);
                    tok->size = intVal;
                    return;
                }
                break;

            // comment
            case 4:
                if (ch == '\n')
                    state = 0;
                break;

            default:
                fprintf(stderr, "Invalid state");
                abort();
        }
    }
}

void Tokenizer::getToken(Token *tok) {
    if (nextTok.type != TOK_INVAL) {
        *tok = nextTok;
        nextTok.type = TOK_INVAL;
    } else {
        scanToken(tok);
    }
}

void Tokenizer::putBack(Token *token) {
    nextTok = *token;
}

